package queped;

import commandlineinterface.ServiceDemandCalculator;
import commandlineinterface.UIEvaluator;
import exceptions.RelationAlreadyInSetException;
import jmt.engine.simDispatcher.Dispatcher_jSIMschema;
import modeller.databasedesignmodel.DatabaseDesign;
import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.transaction.Transaction;
import modeller.queueingnetworksmodel.probabilities.TransactionRoutingMatrix;
import modeller.queueingnetworksmodel.structure.Archive;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import static org.kohsuke.args4j.ExampleMode.ALL;

import results.JSimResultsDisplayer;
import sqlparser.SQLParser;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

import static queped.NeptuneHelperFunctions.isInDebugMode;

/**
 * Created by:  Jason Ye
 * Date:        24/06/2012
 * Time:        20:20
 */
public class QuePEDExecution {

    private double dbpagesize;

    public QuePEDExecution(String[] args) {

        // get command line arguments
        QuePEDOpts options = getCommandLineArgs(args);

        // assign appropriate variables
        inputFile = options.sqlInputFile;
        scalingFactor = options.scalingfactor;
        isStraightToJMT = options.JMT;
        dbIOTime = options.dbIOTime;
        dbpagesize = options.pagesize;
    }

    protected void solveIfRequired(String outputJSIMGFileName) {

        if (isStraightToJMT) {

            solveQN(outputJSIMGFileName);

        }
    }

    /**
     * takes outputted QN and invokes JMT
     * saves QN to file
     * @param outputJSIMGFileName
     */
    private void solveQN(String outputJSIMGFileName) {
        System.out.println("Sending to JMT...");
        String simNodeXMLFilePath = generateFileNameForSIMXMLFile();
        Dispatcher_jSIMschema jsim = new Dispatcher_jSIMschema(simNodeXMLFilePath);

        try {
            boolean isSolved = jsim.solveModel();
            if (isSolved) {
                String[] jsimgFileName = outputJSIMGFileName.split("/");
                String resultsFileName = "temp/res_sim_" + jsimgFileName[jsimgFileName.length - 1] + ".xml";
                System.out.println("JMT Found Solution. Results File saved to: " + resultsFileName);

                // send to HTML
                JSimResultsDisplayer displayer = new JSimResultsDisplayer(resultsFileName);

                displayer.saveHTMLFile(resultsFileName + ".html");

                System.out.println("QN results saved to " + resultsFileName + ".html");

            } else {
                System.out.println("no");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Creates and saves a QN given the database design, Transaction Routing Matrix and Service Demands
     * @param specDesign
     * @param qMatrix
     * @param serviceDemands
     * @return
     */
    protected String generateAndSaveQN(DatabaseDesign specDesign, TransactionRoutingMatrix qMatrix, HashMap<Transaction, IServiceDemand> serviceDemands) {

        // generate appropriate filename
        String outputJSIMGFileName = generateFileNameForJSIMGFile(scalingFactor);

        // generate JDOM XML Document using database design, qMatrix and service demands

        System.out.println("Generating Queueing network ............");
        Document result = generateXMLRepresentation(specDesign, qMatrix, serviceDemands, outputJSIMGFileName);
        System.out.println("QN generated successfully!");

        // save XML document to file in JSIMGraph file format
        System.out.println("Saving QN to file ............");
        saveFile(result, outputJSIMGFileName);
        System.out.println("Saved! ");

        // generate SIM XML file
        // get sim element
        Element archive = result.getRootElement();
        List<Element> archiveChildren = archive.getChildren("sim");
        for (Element e : archiveChildren) {

            if (e.getName().equals("sim")) {
                Element newElem = e.clone();

                Document SIMXMLFile = new Document(newElem);
                saveFile(SIMXMLFile, generateFileNameForSIMXMLFile());
                break;
            }

        }

        return outputJSIMGFileName;

    }

    /**
     * Returns a new Transaction Routing Matrix given the database design
     * @param specDesign
     * @return TransactionRoutingMatrix qMatrix
     */
    protected TransactionRoutingMatrix generateTransactionRoutingMatrix(DatabaseDesign specDesign) {

        System.out.println("Generating transaction routing matrix ............");
        // generate instantaneous transition probability matrix q (customer class path)
        TransactionRoutingMatrix qMatrix = new TransactionRoutingMatrix(specDesign);
        System.out.println("Transaction Routing Matrix generated successfully!");
        return qMatrix;
    }

    /**
     * Creates Service Demands
     * @param specDesign
     * @return Service Demands
     */
    protected HashMap<Transaction, IServiceDemand> generateServiceDemands(DatabaseDesign specDesign) {

        System.out.println("Generating service demands ............");
        ServiceDemandCalculator serviceDemandCalculator = new ServiceDemandCalculator(specDesign);
        HashMap<Transaction, IServiceDemand> serviceDemands = serviceDemandCalculator.calculate();
        System.out.println("Service demands generated successfully!");
        return serviceDemands;

    }

    /**
     * Takes in the Java command line arguments and returns a container containing the command line options
     * @param args
     * @return
     */
    private QuePEDOpts getCommandLineArgs(String[] args) {

        final QuePEDOpts options = new QuePEDOpts();
        final CmdLineParser cmdLineParser = new CmdLineParser(options);

        try {
            cmdLineParser.parseArgument(args);
            if (options.sqlInputFile == null) {
                throw new CmdLineException("Error: SQL Input File Not Specified!\n");
            }
            if(options.dbIOTime==0){
                throw new CmdLineException("Error: Average DB IO Time Not Specified!\n");
            }
        } catch (CmdLineException e) {

            System.err.print(e.getMessage());
            System.err.println("java -jar queped.jar [options ...] ...");
            cmdLineParser.printUsage(System.err);
            System.err.println();

            // print example execution
            System.err.println("  Example: java -jar queped.jar" + cmdLineParser.printExample(ALL));

            //die
            System.exit(1);

        }

        return options;
    }

    /**
     * generates a JDOM Document object which represents the queueing network
     *
     * @param specDesign
     * @param qMatrix
     * @param serviceDemands
     * @param outputJSIMGFileName
     * @return
     */
    private Document generateXMLRepresentation(DatabaseDesign specDesign, TransactionRoutingMatrix qMatrix, HashMap<Transaction, IServiceDemand> serviceDemands, String outputJSIMGFileName) {

        HashMap<Relation, Integer> mapRelationToIndex = constructRelationIndex(specDesign.getRelations());

        String[] path = outputJSIMGFileName.split("/");
        String fileName = path[path.length - 1];

        // generate top level Archive object
        Archive archive = new Archive(specDesign.getRelations(), specDesign.getTransactions(), qMatrix, generateMapIndexToRelation(mapRelationToIndex), fileName, serviceDemands);

        // generate XML
        Element modelElem = archive.toXMLRepresentation();
        Document result = new Document(modelElem);

        // initialiase XMLoutputter
        XMLOutputter outputter = new XMLOutputter();

        // print out xml representation if in debug mode
        if (isInDebugMode()) System.out.print(outputter.outputString(result));

        return result;
    }

    /**
     * Generates the database design
     * @return DatabaseDesign
     */
    protected DatabaseDesign generateDatabaseDesign() {


        // initialise database design to NULL
        DatabaseDesign specDesign = null;

        if (inputFile != null) {

            System.out.println("Parsing SQL from: " + inputFile.getPath() + "..........");
            System.out.println("Setting scaling factor to: " + scalingFactor + "..........");

            SQLParser parser = null;
            try {

                Scanner s = new Scanner(inputFile);
                s.useDelimiter("(;(\r)?\n)|(--\n)");
                parser = new SQLParser(s, scalingFactor,dbIOTime,dbpagesize);

                System.out.println("Parsed SQL successfully!");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                System.out.println("Couldn't parse SQL");
            }

            if (parser != null) {
                specDesign = parser.getDesign();
            }

        } else {
            // if fileName isn't specified on CLI
            UIEvaluator uiEvaluator = new UIEvaluator();

            // user specifies database design
            specDesign = uiEvaluator.initDatabaseDesign();
        }

        // add router node
        Relation router = new Relation(0, 10, 8192, .8);

        router.setRelationName("Router");

        try {
            specDesign.addRelation(router);
        } catch (RelationAlreadyInSetException e) {
            e.printStackTrace();
        }

        System.out.println("Database Design initialised successfully!");

        return specDesign;

    }

    /**
     * given an JDOM XML document, saves it to the savedQNs dir with JSIMG extension
     *
     * @param document
     */
    private String saveFile(Document document, String filename) {

        try {
            // create fileWriter
            FileWriter writer = new FileWriter(filename);

            XMLOutputter xmlOutputter = new XMLOutputter();


            Format format = Format.getPrettyFormat();
            format.setEncoding("ISO-8859-1");

            // Use 'PrettyFormatter' - this removes trailing whitespace either side
            xmlOutputter.setFormat(format);

            // Write doc file and print it.
            xmlOutputter.output(document, writer);

            if (isInDebugMode()) {
                xmlOutputter.output(document, System.out);
            }


        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("JSimGraph file saved to: " + filename);

        return filename;
    }

    private String generateFileNameForJSIMGFile() {
        return generateFileNameForJSIMGFile(1);
    }

    private String generateFileNameForJSIMGFile(int sf) {
        Date date = new Date();
        // format timestamp with year:month:day-hour:minute:sec
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddkkmmss");
        String formattedDate = sdf.format(date);
        String pwd = new File(".").getAbsolutePath();

        return pwd + "/savedQNs/QN" + sf + formattedDate + ".jsimg";
    }

    private String generateFileNameForSIMXMLFile() {
        Date date = new Date();
        // format timestamp with year:month:day-hour:minute:sec
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddkkmmss");
        String formattedDate = sdf.format(date);
        String pwd = new File(".").getAbsolutePath();
        return pwd + "/temp/SIM" + formattedDate + ".xml";
    }

    /**
     * generates an array of the available relations
     *
     * @param mapRelationToIndex
     * @return
     */
    private Relation[] generateMapIndexToRelation(HashMap<Relation, Integer> mapRelationToIndex) {
        Relation[] mapIndexToRelation = new Relation[mapRelationToIndex.size()];
        for (Relation r : mapRelationToIndex.keySet()) {
            mapIndexToRelation[mapRelationToIndex.get(r)] = r;
        }
        return mapIndexToRelation;
    }


    /**
     * generates a lookup table between relations and index for the instantaneous transition probability generator
     *
     * @param relations
     * @return
     */
    private HashMap<Relation, Integer> constructRelationIndex(HashSet<Relation> relations) {

        HashMap<Relation, Integer> mapRelationToIndex = new HashMap<Relation, Integer>();
        int currentIndex = 1;
        for (Relation r : relations) {
            if (r.getRelationName().equals("Router") && r.getBNumDbPages() == 0) {
                mapRelationToIndex.put(r, 0);

            } else {
                mapRelationToIndex.put(r, currentIndex);
                currentIndex++;
            }

        }
        return mapRelationToIndex;

    }

    private File inputFile;
    private int scalingFactor;
    private boolean isStraightToJMT;
    private double dbIOTime;

}
